#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/wait.h>
#include <linux/limits.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#define STDIN_FID 0
#define STDOUT_FID 1

char **parse_command(char *command,char *delimiters);
void cd(char **args, char *dir);
char *buscar(char *program);
bool existe(char *archivo);
char *prompt();
void ejecutar(char **args);
void open_redir_file(char *filename,bool is_input);
void close_redir_file(int next_dup,bool is_input);
bool need_file(char**argstack); 
char **path;
	
int main(int argc,char *argv[]){
    char *input = (char*)malloc(sizeof(char)*50), 
		 *f_input = (char*)malloc(sizeof(char)*50),
		 *dir = (char*)malloc(sizeof(char)*PATH_MAX),
		**arginput,
		**comm_plus_fileout = (char**)malloc(sizeof(char)*2000),
		**comm_plus_filein = (char**)malloc(sizeof(char)*2000);
	int stdin_dup= dup(STDIN_FID),stdout_dup = dup(STDOUT_FID);	
    dir=getcwd(dir,PATH_MAX);								//pido el directorio actual
    path = parse_command(getenv("PATH"),":\0\n");			//pido el path y lo parseo;
 /*   int i;	variable de debugging

	printf("PATH:\n");
	i=0;
	while(path[i] != NULL){
		printf("\t%s\n",path[i]);
		i++;
	}
*/	//descomentar este sector para ver listado el PATH
    while(strstr(input,"exit")==NULL /*&& !feof(stdin)*/){	//si escribis exit sale el programa o tocando ctrl+d
		memset(input,'\0',1);			//pongo un final de linea al inicio de input para evitar que se lea un comando ingresado en una iteracion anterior
		printf("%s ",prompt());
		fgets(input,50,stdin);
		
		if(*input != '\0' && *input != '\n'){	//si se ingresó algo lo analizo, sino no
			
			comm_plus_fileout = parse_command(input,">"); // divido en dos la entrada - comando + filename de output
			input= comm_plus_fileout[0]; // el comando es la primer parte de argstack
			
			if(need_file(comm_plus_fileout)){		
				open_redir_file(comm_plus_fileout[1],false); // abro un archivo con filename que se encuentra en comm_plus_fileout[1]
			}
			comm_plus_filein = parse_command(input,"<"); // divido en dos la entrada - comando + filename de input
			
			if(need_file(comm_plus_filein)){		
				
				open_redir_file(comm_plus_filein[1],true); // abro un archivo de entrada con filename que se encuentra en comm_plus_filein[1]	
				input= comm_plus_filein[0];
				while(!feof(stdin)){
					fgets(f_input,50,stdin);
					input = strcat(input,f_input);
				}
				rewind(stdin);
			
			} 
			
			arginput = parse_command(input," \0\n");

			//~ int i=0;
			//~ printf("arginput:\n");
			//~ while(arginput[i] != NULL){
				//~ printf("\t%s\n",arginput[i]);
				//~ i++;
			//~ }
			if(strcmp(arginput[0],"cd")==0){
				cd(arginput,dir);
			}else if(strcmp(input,"exit")!=0){
				ejecutar(arginput);
			}
			close_redir_file(stdout_dup,false);
			close_redir_file(stdin_dup,true);
		}
    }
    printf("\n");
    return EXIT_SUCCESS;
}
/**
 * @param argstack es un array de char* separado por caracteres especiales < >
 * @return true si necesita abrir un archivo para redireccionar entrada o salida
 * 
 */
bool need_file(char **argstack){
	int i=0;
	while(argstack[i] != NULL){i++;} // cuento la cantidad de elementos de argstack
	if(i<1 || i>2) {perror("mas de un archivo de salida, solo se acepta un unico simbolo '>'"); exit(1);}
	else return i==2;
}

void open_redir_file(char *filename, bool is_input){
	int fid;
	int flags,perm;
	int oldfid;
	if (is_input){
		oldfid=STDIN_FID;
		flags = O_RDONLY; 
		perm = S_IRUSR;
		while(filename != strtok(filename," \0\n")){
			filename= strtok(filename," \0\n");
		}
	}else{
		oldfid=STDOUT_FID;
		flags = O_WRONLY|O_CREAT|O_TRUNC;
		if(filename[0] == ' ') filename = strtok(filename," "); 
		filename= strtok(filename,"\n");
		strcat(filename,".out");
		perm = S_IWUSR|S_IRUSR;
	}
	fid = open(filename, flags, perm);
	if (fid<0) { 
		perror("open");
		exit(1); 
	}	
	close(oldfid);
	if (dup2(fid,oldfid)<0) { 
		perror("dup"); 
		exit(1); 
	}
	close(fid);
}

void close_redir_file(int next_dup, bool is_input){
	int std=1;
	if (is_input){std=0;}
	if (dup2(next_dup,std)<0) { 
		perror("dup"); 
		exit(1); 
	}
}
/**
 * cd = change directory
 * intenta cambiar directorio. Si no lo logra imprime un mensaje de error
 * @param char **args array de argumentos. Comienza con el nombre del programa y sigue con los argumentos
 * @param char * dir directorio. Si hay cambio de directorio se modifica
 */
void cd(char **args, char *dir){
	int err;
	if(args[1]==NULL){
		err = chdir(getenv("HOME"));		// si no se especifica directorio va al home del usuario
	}else{
		if(args[1][0]=='~'){				// si encuentra ~ le concatena al principio el path del directorio home del usuario
			char *aux = getenv("HOME");
			strcat(aux,(args[1]+1));
			strcpy(args[1],aux);
		}
		if(strcmp(args[1],"\0")==0)
			err = chdir(getenv("HOME"));	// si el directorio destino es vacío va al home del usuario
		else
			err = chdir(args[1]);			// si el directorio no es referido al home, toma la direccion absoluta o relativa a la carpeta actual
	}
	if(err==0){
		dir = getcwd(dir,PATH_MAX);				// si no hubo error en el cambio de directorio, actualizo el directorio actual en memoria
	}else printf("Error. Verifique que la ruta seleccionada exista\n");
}

/**
 * @param command es la línea a parsear
 * @param delimeters es un string que contiene los caracteres usados como delimitadores
 * @return un array de los token presentes en la linea pasada como parametro
 */ 
char **parse_command(char *command,char *delimiters){
	int i = 0;
	char **result = (char**)malloc(sizeof(char)*200);
    result[0] = strcat(strtok(command,delimiters),"\0");			//le paso el string a analizar a strtok y me da el primer token
    while(result[i] != NULL){
		result[++i] = strcat(strtok(NULL,delimiters),"\0");			//pido el siguiente token
	}
	i--;
	/* COMENTADO PORQUE, LO RESOLVI MAS ABAJO DE OTRA FORMA QUE ES MAS NEUTRAL
	int j = 0;
	while(result[i][j] != '\0')	//se agrega un \n al ultimo parametro... con este bucle itero hasta encontrar el final de string
		j++;
	//j--;
	result[i][j] = '\0';	//reemplazo el \n por un \0
	*/
	result [i]= strtok(result[i],"\n"); // resolvi lo mismo de arriba pero con esto, porque en algunos casos el "j--" la caga
	return result;
}
/**
 * @param path es el array de char* que contiene todos los lugares a buscar
 * @param program es el nombre del programa a buscar
 * @return el path del programa si existe, o NULL si no existe
 */
char *buscar(char *program){
	char *pathfile = (char*)malloc(sizeof(char)*50);
	if(strstr(program,"/")!=NULL){
		if(existe(program)){
			return program;
		}else return NULL;
	}else{
		int i = 0;
		while(path[i] != NULL){
			strcpy(pathfile,path[i]);
			strcat(pathfile,"/");
			strcat(pathfile,program);
			if(existe(pathfile)){	//si no esta ahi el archivo, vamos por el siguiente path
				return pathfile;	//si ya lo encontramos, devuelvo el path completo del programa y termina la busqueda
			}else i++;
		}
		return NULL;
	}
}
/**
 * @param direccion (absoluta o relativa al directorio actual) del archivo a ejecutar
 * @return true si el archivo existe, false sino
 */
bool existe(char *archivo){
	return (fopen(archivo,"r") != NULL);	//si fopen devuelve NULL quiere decir que no esta ahi el archivo
}

char *prompt(){
	char *dir = (char*)malloc(sizeof(char)*PATH_MAX), *home = getenv("HOME");
	dir = getcwd(dir,PATH_MAX);
	if(strstr(dir,home) != NULL){
		dir = dir + strlen(home)-1;
		*dir = '~';
	}
	strcat(dir,"$");
	return dir;
}

void ejecutar(char **args){
	int pid,status,i = -1;
	bool background;
	while(args[++i] != NULL){
		if(strcmp(args[i],"|") == 0 || strcmp(args[i],"<") == 0 || strcmp(args[i],">") == 0){
			i++;
			break;
		}
	}
	if(strcmp(args[--i],"&") == 0){
		background = true;
		args[i] = NULL;
	}
	if((pid = fork()) < 0){		// se duplican los procesos. El padre recibe el PID del hijo, el hijo recibe un 0
		printf("Error creando el hijo \n");
	}else if(pid == 0){		// aca entra el proceso hijo
		if(execv(buscar(args[0]),args) < 0){	//si hay un error en la ejecución del programa
			printf("%s: no se encontró la orden\n",args[0]);
			exit(1);	//termina el proceso hijo con una señal de error por el programa no encontrado
		}
	}else{					// pid > 0, entra solo el padre
		if(!background)
			status = waitpid(pid,&status,0);	//espera a que termine el hijo para continuar
		else
			printf("[1] %d\n",pid);
	}
}
	

